﻿using DBUtil.FastEntity;
using DBUtil.FastEntity.Annotations;
using DotNetCommon.Extensions;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
using System.Reflection;
using System.Text;

namespace DBUtil
{
    /// <summary>
    /// DBAccess扩展类
    /// </summary>
    public static class DBAccessExtensions
    {
        private static ConcurrentDictionary<string, Dictionary<Type, EntityInfo>> _caches = new ConcurrentDictionary<string, Dictionary<Type, EntityInfo>>();
        public static EntityManager<T> GetEntityManager<T>(this DBAccess db, Action<EntityInfo> action = null) where T : class, new()
        {
            var entityInfo = GetEntityInfo<T>(db);
            action?.Invoke(entityInfo);
            return new EntityManager<T>(db, entityInfo);
        }

        public static EntityInfo GetEntityInfo<T>(this DBAccess db) => GetEntityInfo(db, typeof(T));

        private static EntityInfo GetEntityInfo(DBAccess db, Type type)
        {
            var dic = _caches.GetOrAdd(db.DBConn, _ => new Dictionary<Type, EntityInfo>());
            EntityInfo entityInfo;
            if (dic.ContainsKey(type)) entityInfo = dic[type];
            lock (dic)
            {
                if (dic.ContainsKey(type)) entityInfo = dic[type];
                else
                {
                    GenerateEntityInfo(type, dic);
                    entityInfo = dic[type];
                }
            }
            entityInfo = entityInfo.Clone();
            if (db.Settings.GetExtend<EntitySetting>(EntitySetting.ExtendKey).EntityInfoAfter.IsNotNullOrEmpty())
            {
                var arg = new EntityInfoAfterArgument()
                {
                    db = db,
                    EntityInfo = entityInfo
                };
                db.Settings.GetExtend<EntitySetting>(EntitySetting.ExtendKey).EntityInfoAfter.ForEach(act => act(arg));
            }
            return entityInfo;
        }

        private static void GenerateEntityInfo(Type type, Dictionary<Type, EntityInfo> dic)
        {
            var entityInfo = new EntityInfo();
            dic.Add(type, entityInfo);
            var attrs = type.GetCustomAttributes();
            var tableAttr = attrs.FirstOrDefault(i => i is TableAttribute) as TableAttribute;
            //TableName SchemaName
            if (tableAttr != null)
            {
                entityInfo.TableName = tableAttr.Name;
                entityInfo.SchemaName = tableAttr.Schema;
                if (entityInfo.SchemaName.IsNotNullOrEmptyOrWhiteSpace()) entityInfo.FullName = $"{tableAttr.Schema}.{tableAttr.Name}";
                else entityInfo.FullName = entityInfo.TableName;
            }
            else
            {
                //class: InnerClass<int>
                entityInfo.FullName = entityInfo.TableName = type.Name.Split('`')[0];
            }
            entityInfo.Type = type;
            entityInfo.TypeFullName = type.GetClassFullName();
            //tree
            var tableTree = attrs.FirstOrDefault(i => i is TableTreeAttribute) as TableTreeAttribute;
            if (tableTree != null)
            {
                entityInfo.IsTree = true;
                entityInfo.TreeType = tableTree.TreeType;
            }
            else
            {
                entityInfo.IsTree = false;
            }

            //audit

            //properties
            var tmp = type.GetProperties(BindingFlags.Public | BindingFlags.Instance);
            //只读的忽略
            var props = tmp.Where(i => i.CanWrite).ToList();
            for (int i = 0, count = props.Count; i < count; i++)
            {
                var prop = props[i];
                var propAttrs = prop.GetCustomAttributes();
                var entityPropertyInfo = new EntityPropertyInfo();
                entityPropertyInfo.PropertyInfo = prop;
                entityPropertyInfo.PropertyType = prop.PropertyType;
                entityPropertyInfo.DefaultValue = prop.PropertyType.GetDefault();
                entityInfo.EntityPropertyInfos.Add(entityPropertyInfo);

                //基本信息
                #region 基本信息               

                //基本信息
                var propType = prop.PropertyType;
                if (propType.IsNullable()) propType = propType.GenericTypeArguments[0];
                if (simpleTypes.Contains(propType) || propType.IsEnum)
                {
                    //简单类型
                    entityPropertyInfo.PropName = prop.Name;
                    entityPropertyInfo.IsColumn = true;
                    //entityPropertyInfo.TypeNum = EntityUtil.GetTypeNum(prop);
                }
                else
                {
                    //类
                    //再过滤掉storejson storejsonfast的
                    var storeJsonAttr = propAttrs.FirstOrDefault(i => i is StoreJsonAttribute) as StoreJsonAttribute;
                    var storeJsonFastAttr = propAttrs.FirstOrDefault(i => i is StoreJsonFastAttribute) as StoreJsonFastAttribute;
                    if (storeJsonAttr == null && storeJsonFastAttr == null)
                    {
                        GenerateEntityInfo(propType, dic);
                        entityPropertyInfo.IsColumn = false;
                        continue;
                    }
                    if (storeJsonFastAttr != null)
                    {
                        entityPropertyInfo.IsStoreJsonFast = true;
                        entityPropertyInfo.StoreJsonFastDateFormatString = storeJsonFastAttr.DateFormatString;
                        entityPropertyInfo.StoreJsonFastIsLongToString = storeJsonFastAttr.IsLongToString;
                        entityPropertyInfo.StoreJsonFastIgnoreNull = storeJsonFastAttr.IgnoreNull;
                        entityPropertyInfo.StoreJsonFastEnum2String = storeJsonFastAttr.Enum2String;
                        entityPropertyInfo.StoreJsonFastLowerCamelCase = storeJsonFastAttr.LowerCamelCase;
                        entityPropertyInfo.StoreJsonFastIsIntend = storeJsonFastAttr.IsIntend;
                        entityPropertyInfo.StoreJsonFastIsAllToString = storeJsonFastAttr.IsAllToString;
                        entityPropertyInfo.StoreJsonFastAllNumDigit = storeJsonFastAttr.AllNumDigit;
                        entityPropertyInfo.StoreJsonFastDecimalDigit = storeJsonFastAttr.DecimalDigit;
                        entityPropertyInfo.IsColumn = true;
                    }
                    else
                    {
                        entityPropertyInfo.IsStoreJson = true;
                        entityPropertyInfo.IsColumn = true;
                    }
                }

                var colAttr = propAttrs.FirstOrDefault(i => i is ColumnAttribute) as ColumnAttribute;
                if (colAttr != null)
                {
                    entityPropertyInfo.ColumnName = colAttr.Name;
                    entityPropertyInfo.TypeName = colAttr.TypeName;
                    entityPropertyInfo.Order = colAttr.Order;
                }
                else
                {
                    entityPropertyInfo.ColumnName = prop.Name;
                }
                #endregion

                //ignore
                #region Ignore
                var isNotMapped = propAttrs.Any(i => i is NotMappedAttribute);
                if (isNotMapped)
                {
                    entityPropertyInfo.IsIgnoreInsert = entityPropertyInfo.IsIgnoreUpdate = entityPropertyInfo.IsIgnoreSelect = entityPropertyInfo.IsIgnoreWrite = true;
                }
                else
                {
                    var isIgnoreWrite = propAttrs.Any(i => i is IgnoreWriteAttribute);
                    if (isIgnoreWrite)
                    {
                        entityPropertyInfo.IsIgnoreInsert = entityPropertyInfo.IsIgnoreUpdate = entityPropertyInfo.IsIgnoreWrite = true;
                    }
                    else
                    {
                        entityPropertyInfo.IsIgnoreUpdate = propAttrs.Any(i => i is IgnoreUpdateAttribute);
                        entityPropertyInfo.IsIgnoreInsert = propAttrs.Any(i => i is IgnoreInsertAttribute);
                        entityPropertyInfo.IsIgnoreSelect = propAttrs.Any(i => i is IgnoreSelectAttribute);
                    }
                }
                #endregion

                //主键
                #region 主键
                var primaryAttr = propAttrs.FirstOrDefault(i => i is PrimaryKeyAttribute) as PrimaryKeyAttribute;
                if (primaryAttr != null)
                {
                    entityPropertyInfo.IsPrimaryKey = true;
                    entityPropertyInfo.PrimaryKeyStrategy = primaryAttr.KeyStrategy;
                    entityPropertyInfo.PrimaryKeyArg = primaryAttr.Arg;
                    if (primaryAttr.KeyStrategy == KeyStrategy.CustomGenerator)
                        entityPropertyInfo.EntityIdGenerator = Activator.CreateInstance(primaryAttr.Arg as Type) as IEntityIdGenerator;
                }
                else
                {
                    entityPropertyInfo.IsPrimaryKey = false;
                }
                #endregion

                //树
                #region 树
                if (entityInfo.IsTree)
                {
                    entityPropertyInfo.IsTableTreeId = propAttrs.Any(i => i is TableTreeIdAttribute);
                    entityPropertyInfo.IsTableTreeParentId = propAttrs.Any(i => i is TableTreeParentIdAttribute);
                    entityPropertyInfo.IsTableTreeChildren = propAttrs.Any(i => i is TableTreeChildrenAttribute);
                    var tableTreeIdSegmentAttr = propAttrs.FirstOrDefault(i => i is TableTreeIdSegmentAttribute) as TableTreeIdSegmentAttribute;
                    if (tableTreeIdSegmentAttr != null) entityPropertyInfo.TableTreeIdSegment = tableTreeIdSegmentAttr.SplitChar;
                }
                #endregion

                //audit
                #region audit
                entityPropertyInfo.IsAuditCreateTime = propAttrs.Any(i => i is AuditCreateTimeAttribute);
                entityPropertyInfo.IsAuditCreateUserId = propAttrs.Any(i => i is AuditCreateUserIdAttribute);
                entityPropertyInfo.IsAuditUpdateTime = propAttrs.Any(i => i is AuditUpdateTimeAttribute);
                entityPropertyInfo.IsAuditUpdateUserId = propAttrs.Any(i => i is AuditUpdateUserIdAttribute);
                entityPropertyInfo.IsAuditDeleteTime = propAttrs.Any(i => i is AuditDeleteTimeAttribute);
                entityPropertyInfo.IsAuditDeleteUserId = propAttrs.Any(i => i is AuditDeleteUserIdAttribute);

                var tmp0 = propAttrs.FirstOrDefault(i => i is AuditCreateUserIdAttribute) as AuditCreateUserIdAttribute;
                if (tmp0 != null)
                {
                    entityPropertyInfo.AuditDefaultValue = tmp0.Default;
                }
                var tmp2 = propAttrs.FirstOrDefault(i => i is AuditUpdateUserIdAttribute) as AuditUpdateUserIdAttribute;
                if (tmp2 != null)
                {
                    entityPropertyInfo.IsAuditInitUseCreateValue = tmp2.InitUseCreateValue;
                    entityPropertyInfo.AuditDefaultValue = tmp2.Default;
                }
                var tmp3 = propAttrs.FirstOrDefault(i => i is AuditUpdateTimeAttribute) as AuditUpdateTimeAttribute;
                if (tmp3 != null)
                {
                    entityPropertyInfo.IsAuditInitUseCreateValue = tmp3.InitUseCreateValue;
                }
                var tmp4 = propAttrs.FirstOrDefault(i => i is AuditDeleteUserIdAttribute) as AuditDeleteUserIdAttribute;
                if (tmp4 != null)
                {
                    entityPropertyInfo.AuditDefaultValue = tmp4.Default;
                }
                #endregion

                //ReferHits
                #region ReferHit
                var hits = propAttrs.Where(i => i is ReferHitAttribute).Select(i => i as ReferHitAttribute).ToList();
                entityPropertyInfo.ReferHits = new List<EntityReferHitInto>();
                if (hits.Any())
                {
                    for (int j = 0, length = hits.Count; j < length; j++)
                    {
                        var hit = hits[j];
                        var referHit = new EntityReferHitInto()
                        {
                            ReferName = hit.ReferName,
                            SplitString = hit.SplitString,
                            ReferHitType = hit.ReferHitType,
                            ReferTable = hit.ReferTable,
                            ReferProp = hit.ReferProp,
                            StaticReferProp1 = hit.StaticReferProp1,
                            StaticReferProp2 = hit.StaticReferProp2,
                            StaticReferProp3 = hit.StaticReferProp3,

                            StaticReferValue1 = hit.StaticReferValue1,
                            StaticReferValue2 = hit.StaticReferValue2,
                            StaticReferValue3 = hit.StaticReferValue3,

                            StaticThisValue = hit.StaticThisValue,

                            //最后填充
                            //ReferEntityInfo，
                            //ReferPropEntityPropertyInfo
                            //StaticReferProp1PropertyInfo
                            //StaticReferProp2PropertyInfo
                            //StaticReferProp3PropertyInfo                            
                        };
                        entityPropertyInfo.ReferHits.Add(referHit);
                    }
                }
                #endregion

                //UseReferHitTable
                #region UseReferHitTable
                var useReferHitTableAttr = propAttrs.FirstOrDefault(i => i is UseReferHitTableAttribute) as UseReferHitTableAttribute;
                if (useReferHitTableAttr != null)
                {
                    entityPropertyInfo.IsUseReferHitTable = true;
                    entityPropertyInfo.UseReferHitTable_UseReferHitTable = useReferHitTableAttr.HitTable;
                    //UseReferHitTable_UseReferHitTableEntityInfo 最后填充
                }
                #endregion

                //ReferColumn
                #region ReferColumn
                var referColumnAttr = propAttrs.FirstOrDefault(i => i is ReferColumnAttribute) as ReferColumnAttribute;
                if (useReferHitTableAttr != null)
                {
                    entityPropertyInfo.IsReferColumn = true;
                    entityPropertyInfo.ReferColumn_ReferHint = referColumnAttr.ReferHint;
                    entityPropertyInfo.ReferColumn_ReferProp = referColumnAttr.ReferProp;
                    //entityPropertyInfo.ReferColumn_ReferPropEntityPropertyInfo 最后填充
                }
                else
                {
                    entityPropertyInfo.IsReferColumn = false;
                }
                #endregion

                //ReferTable
                #region ReferTable
                var referTableAttr = propAttrs.FirstOrDefault(i => i is ReferTableAttribute) as ReferTableAttribute;
                if (referTableAttr != null)
                {
                    entityPropertyInfo.IsReferTable = true;
                    entityPropertyInfo.ReferTable_ReferHint = referTableAttr.ReferHint;
                    //entityPropertyInfo.ReferTable_ReferProp 最后填充
                }
                else
                {
                    entityPropertyInfo.IsReferColumn = false;
                }
                #endregion
            }

            //填充信息
        }

        private static List<Type> simpleTypes = new List<Type>
        {
            typeof(byte),typeof(sbyte),
            typeof(short),typeof(ushort),
            typeof(int),typeof(uint),
            typeof(long),typeof(ulong),
            typeof(float),typeof(double),
            typeof(bool),
            typeof(char),
            typeof(decimal),
            typeof(string),
            typeof(DateTime),
            typeof(DateTimeOffset),
            typeof(Guid),
            typeof(TimeSpan),
            typeof(byte[]),
#if NET6_0_OR_GREATER
            typeof(DateOnly),typeof(TimeOnly),
#endif
        };
    }
}
